//-------------------------------------------------------------------
//	Copyright (c) 2013  Zhirnov Andrey
//	This file is part of the "Noise Library" project.
//	See copyright notice in "linNoiseCL.h".
//-------------------------------------------------------------------

#pragma once

#include "../CSource.h"
#include "CSourceOutput.h"

namespace Noise
{
	
	//
	// Function Source for output value with gradient color
	//

	class CSourceGradient : public ISourceOutput
	{
	// types
	protected:
		typedef CSourceGradient	Self;

		struct GradientPoint
		{
			real	value;
			vec4	color;
		};

		typedef array< GradientPoint >	gradient_t;


	// variables
	protected:
		CSourcePtr		_source;
		gradient_t		_gradient;
		
		static const
			ESource		_sourceType = GRADIENT;

	// methods
	public:
		CSourceGradient ()
		{}

		~CSourceGradient ()
		{}

		ESourceState GetState () const
		{
			return _source.IsNotNull() ? SOURCE_COMPLETE : UNCOMPLETE_ATTACHMENT_SOURCE;
		}
		
		virtual void Delete ()
		{
			_source	= null;
		}

		virtual EResult GetInteger (ESourceQuery type, int *v, int count) const
		{
			CHECK_ARGUMENT_R( count >= 1, INVALID_VALUE );

			switch ( type )
			{
				case SOURCE_STATE :
					v[0] = GetState();
					return OK;

				case SOURCE_TYPE :
					v[0] = _sourceType;
					return OK;

				case SOURCES_COUNT : {
					int	count = 0;
					_source->GetInteger( type, &count, 1 );
					v[0] = count + 1;
					return OK;
				}

				case INPUT_SOURCES :
					v[0] = 1;
					return OK;
			}
			return INVALID_ENUM;
		}
		
		virtual EResult SetArg (EArgument type, CSourcePtr src)
		{
			switch ( type )
			{
				case SOURCE_0 :	_source = src;	return OK;
			}
			return INVALID_ENUM;
		}

		virtual EResult SetArg (EArgument type, const real *v, int count)
		{
			CHECK_ARGUMENT_R( !(count % 5), INVALID_VALUE );
			STATIC_ASSERTE( sizeof(GradientPoint) == sizeof(v[0])*5 );

			switch ( type )
			{
				case ARG_ADD_COLOR_POINTS :
					CHECK_ARGUMENT_R( count >= 5, INVALID_VALUE );
					return AddPoints( (GradientPoint *)v, count/5 ) ? OK : INVALID_VALUE;

				case ARG_COLOR_POINTS :
					_gradient.ClearMem();
					return AddPoints( (GradientPoint *)v, count/5 ) ? OK : INVALID_VALUE;
			}
			return INVALID_ENUM;
		}

		virtual EResult GetArg (EArgument type, CSourcePtr &src) const
		{
			switch ( type )
			{
				case SOURCE_0 :	src = _source;	return OK;
			}
			return INVALID_ENUM;
		}
		
		virtual EResult GetArg (EArgument type, real *v, int count) const
		{
			CHECK_ARGUMENT_R( count >= 5 and !(count % 5), INVALID_VALUE );

			switch ( type )
			{
				case ARG_COLOR_POINTS :
					MemCopy( v, _gradient.ptr(), Min( count * sizeof(real), _gradient.Size() ) );
					return OK;
			}
			return INVALID_ENUM;
		}

		bool AddPoints (const GradientPoint *points, int count)
		{
			bool result = true;

			for (int i = 0; i < count; ++i)
			{
				int pos = FindInsertionPos( points[i].value );
				
				if ( pos == -1 )
					result = false;

				_gradient.Insert( points[i], pos );
			}
			return result;
		}
		
		int FindInsertionPos (real inputValue) const
		{
			uint insertionPos;
			for (insertionPos = 0; insertionPos < _gradient.Count(); ++insertionPos)
			{
				if ( inputValue < _gradient[insertionPos].value )	break;		else
				if ( inputValue == _gradient[insertionPos].value )	return -1;
			}
			return insertionPos;
		}
		
		virtual CSourcePtr Clone () const override
		{
			Self * result = new Self();
			result->_gradient = this->_gradient;
			return result;
		}
		
		virtual void PreBuild (const TPreBuildData &data) override
		{
			data.Push( _source.ptr() );
		}

		virtual bool BuildFunction (TFunctionData &data) override
		{
			CHECK_VALUE( not _gradient.Empty() );

			data.source
				<< "real4 " << CSource::FunctionName() << _id << " (real3 vPoint)\n{\n"
				<< "const real value = ";
			CSource::BuildFunctionCall( data.source, _source->GetSourceId() ) << ";\n"
				<< "const int gradientCount = " << _gradient.Count() << ";\n"
				<< "const real gradientValues[" << _gradient.Count() << "] = {";

			FOR( i, _gradient )
			{
				data.source << '\n' << _gradient[i].value << ',';
			}
			data.source.PopBack();
			data.source
				<< "\n};\n"
				<< "const real4 gradientColors[" << _gradient.Count() << "] = {";

			FOR( i, _gradient )
			{
				data.source
					<< "\n(real4)( " << _gradient[i].color[0]
					<< ", " << _gradient[i].color[1]
					<< ", " << _gradient[i].color[2]
					<< ", " << _gradient[i].color[3]
					<< " ),";
			}
			data.source.PopBack();
			data.source
				<< "\n};\n"
				<< TOSTRING(
					int index = 0;\n
					for (; index < gradientCount; ++index) {\n
						if ( value < gradientValues[index] ) {\n
							break;\n
						}\n
					}\n
					\n
					const int  prev_index	= max( index - 1, 0 );\n
					const real next_value	= gradientValues[index];\n
					const real prev_value	= gradientValues[prev_index];\n
					const real alpha		= clamp( (value - prev_value) / (next_value - prev_value), 0.0, 1.0 );\n
					\n
					return mix( gradientColors[prev_index], gradientColors[index], alpha );\n
				) << "}\n";

			return true;
		}
		
		NATIVE(
		virtual float32 NativeFunction32 (const fvec3 &vPoint) override
		{
			WARNING( "this function is not supported, use NativeFunction4" );
			return 0;
		}

		virtual float64 NativeFunction64 (const dvec3 &vPoint) override
		{
			WARNING( "this function is not supported, use NativeFunction4" );
			return 0;
		}

		template < typename Real, Real (CSource::*NativeFunction)(const TVec<Real,3> &) >
		TVec<Real,4> NativeFunction4 (const TVec<Real,3> &vPoint)
		{
			typedef TVec<Real,4>	Real4;
			
			CHECK_VALUE_R( not _gradient.Empty(), Real4() );

			const Real value = (_source.ptr()->*NativeFunction)( vPoint );
			
			uint index = 0;
			FORc( index, _gradient ) {
				if ( value < _gradient[index].value ) {
					break;
				}
			}

			const uint prev_index	= Max<uint>( index - 1, 0 );
			const Real next_value	= (Real) _gradient[index].value;
			const Real prev_value	= (Real) _gradient[prev_index].value;
			const Real alpha		= Clamp( (value - prev_value) / (next_value - prev_value), Real(0.0), Real(1.0) );

			return Mix( _gradient[prev_index].color.To<Real4>(), _gradient[index].color.To<Real4>(), alpha );
		}
		
		virtual fvec4 NativeFunction4_32 (const fvec3 &vPoint) override
		{
			return NativeFunction4< float32, &CSource::NativeFunction32 >( vPoint );
		}
		
		virtual dvec4 NativeFunction4_64 (const dvec3 &vPoint) override
		{
			return NativeFunction4< float64, &CSource::NativeFunction64 >( vPoint );
		})

		virtual EPixelFormat GetFormat () const override
		{
			return RGBA;
		}

		static bool CreateSource (CSourcePtr &src, CContext *context, ESource type)
		{
			ASSERT( type == _sourceType );

			src = new Self();
			return true;
		}
	};

}	// Noise